/*
 * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke
 *
 * This file is part of SeMoLearning.
 *
 * SeMoLearning is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * SeMoLearning is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with SeMoLearning.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "semol/core/DBTrash.h"
#include "semol/core/DBDriver.h"
#include "semol/core/Node.h"

#include <utilite/UConversion.h>
#include <utilite/UMath.h>
#include <utilite/ULogger.h>
#include <utilite/UTimer.h>
#include <utilite/UStl.h>

namespace semol {

DBTrash::DBTrash(DBDriver * dbDriver) :
	_emptyTrashesTime(0),
	_dbDriver(dbDriver)
{
	UASSERT(_dbDriver != 0);
}

DBTrash::~DBTrash()
{
	this->join(true);
	this->emptyTrashes();
}

void DBTrash::mainLoop()
{
	this->emptyTrashes();
	this->kill(); // Do it only once
}

void DBTrash::emptyTrashes(bool async)
{
	if(async)
	{
		UDEBUG("Async emptying, start the trash thread");
		this->start();
		return;
	}

	std::map<int, Node*> nodes;
	std::map<int, Sensor*> sensors;
	_trashesMutex.lock();
	{
		UDEBUG("nodes=%d, sensors=%d", _trashNodes.size(), _trashSensors.size());
		nodes = _trashNodes;
		sensors = _trashSensors;
		_trashNodes.clear();
		_trashSensors.clear();
	}
	_trashesMutex.unlock();

	UTimer totalTime;
	totalTime.start();

	if(nodes.size() || sensors.size())
	{
		if(_dbDriver->isConnected())
		{
			_dbDriver->beginTransaction();
		}

		UTimer timer;
		timer.start();
		if(nodes.size())
		{
			if(_dbDriver->isConnected())
			{
				//Only one query to the database
				_dbDriver->saveOrUpdate(uValues(nodes));
			}

			for(std::map<int, Node *>::iterator iter=nodes.begin(); iter!=nodes.end(); ++iter)
			{
				delete iter->second;
			}
			nodes.clear();
			UDEBUG("Time emptying memory signatures trash = %f...", timer.ticks());
		}
		if(sensors.size())
		{
			if(_dbDriver->isConnected())
			{
				//Only one query to the database
				_dbDriver->saveOrUpdate(uValues(sensors));
			}

			for(std::map<int, Sensor *>::iterator iter=sensors.begin(); iter!=sensors.end(); ++iter)
			{
				delete (*iter).second;
			}
			sensors.clear();
			UDEBUG("Time emptying memory sensors trash = %f...", timer.ticks());
		}

		if(_dbDriver->isConnected())
		{
			_dbDriver->commit();
		}
	}

	_emptyTrashesTime = totalTime.ticks();
	UDEBUG("Total time emptying trashes = %fs...", _emptyTrashesTime);
}

// ownership transferred
void DBTrash::asyncSave(Node * node)
{
	if(node)
	{
		UDEBUG("node=%d saved=%d modified=%d", node->id(), node->isSaved()?1:0, node->isModified()?1:0);
		_trashesMutex.lock();
		{
			_trashNodes.insert(std::pair<int, Node*>(node->id(), node));
		}
		_trashesMutex.unlock();
	}
}

// ownership transferred
void DBTrash::asyncSave(Sensor * sensor)
{
	if(sensor)
	{
		UDEBUG("sensor=%d", sensor->id());
		_trashesMutex.lock();
		{
			_trashSensors.insert(std::pair<int, Sensor*>(sensor->id(), sensor));
		}
		_trashesMutex.unlock();
	}
}

std::list<Node *> DBTrash::loadNodes(const std::list<int> & nodeIds)
{
	UDEBUG("");
	// look up in the trash before the database
	std::list<int> ids = nodeIds;
	std::list<Node *> nodes;
	_trashesMutex.lock();
	{
		for(std::list<int>::iterator iter = ids.begin(); iter != ids.end();)
		{
			std::map<int, Node*>::iterator sIter = _trashNodes.find(*iter);

			if(sIter != _trashNodes.end())
			{
				nodes.push_back(sIter->second);
				_trashNodes.erase(sIter);
				iter = ids.erase(iter);
			}
			else
			{
				++iter;
			}
		}
	}
	_trashesMutex.unlock();
	if(ids.size() && _dbDriver->isConnected())
	{
		std::list<Node *> dbNodes = _dbDriver->loadNodes(ids);
		uAppend(nodes, dbNodes);
	}
	return nodes;
}

std::map<int, Sensor *> DBTrash::loadSensors(const std::set<int> & sensorIds)
{
	UDEBUG("");
	// look up in the trash before the database
	std::set<int> ids = sensorIds;
	std::map<int, Sensor *> sensors;
	_trashesMutex.lock();
	{
		for(std::set<int>::iterator iter = ids.begin(); iter != ids.end();)
		{
			std::map<int, Sensor*>::iterator sIter = _trashSensors.find(*iter);

			if(sIter != _trashSensors.end())
			{
				sensors.insert(std::pair<int, Sensor*>(sIter->first, sIter->second));
				_trashSensors.erase(sIter);
				ids.erase(iter++);
			}
			else
			{
				++iter;
			}
		}
	}
	_trashesMutex.unlock();
	if(ids.size() && _dbDriver->isConnected())
	{
		std::map<int, Sensor *> dbSensors = _dbDriver->loadSensors(ids);
		sensors.insert(dbSensors.begin(), dbSensors.end());
	}
	return sensors;
}

} // namespace rtabmap
